package webdriver;

import static webdriver.Logger.getLoc;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.naming.NamingException;

import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;

import org.junit.Assert;
import org.openqa.selenium.Alert;
import org.openqa.selenium.By;
import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import webdriver.elements.BaseElement;

import com.google.common.base.Strings;

/**
 * The main class to access the browser, which extends the capabilities of the standard Webdriver
 */
public final class Browser {

	private static final long SLEEP_SWITCH = 30000l;
	private static final long SLEEP_THREAD_SLEEP = 250;
	private static final long IMPLICITY_WAIT = 10;
	private static final String DEFAULT_CONDITION_TIMEOUT = "defaultConditionTimeout";
	private static final String DEFAULT_PAGE_LOAD_TIMEOUT = "defaultPageLoadTimeout";
	private static final String DEFAULT_SIKULI_TIMEOUT = "defaultSikuliTimeout";
	private static final String DEFAULT_SIKULI_SIMILARITY = "defaultSikuliSimilarity";
	private static final String URL_LOGIN_PAGE = "urlLoginPage";
	private static final String DETECT_JS_ERRORS = "detectJsErrors";
	private static final String TROUBLE_SHOOTING = "troubleShooting";
	private static final String ANALYZE_TRAFFIC = "analyzeTraffic";
	
	// имя файла с настройками Selenium
	/**
	 * speedValue=100 defaultPageLoadTimeout=60 defaultConditionTimeout=180
	 * urlLoginPage=http://opensesame:%23pears0n@dev.pearsoninnovationlabs.com/ #overrided in
	 * Browser.initProperties-default=firefox; #if null - use argument 'browser' to JVM; if other value (without '*'),
	 * #will use it as browserStartCommand #Usage: #firefox #iexplore #chrome #null browser=iexplore
	 */
	static final String PROPERTIES_FILE = "selenium.properties";
	private static final String BROWSER_BY_DEFAULT = "firefox";
	private static final String BROWSER_FIREFOX = "firefox";
	private static final String BROWSER_CHROME = "chrome";
	private static final String BROWSER_PROP = "browser";
	private static final String STAGE = "stage";
	private static final String START_URL = "startUrl";

	// browsers
	private static Browser instance;
	private static RemoteWebDriver driver;
	private static Logger logger = Logger.getInstance();
	public static PropertiesResourceManager props;

	// поля класса
	private static String browserURL;
	private static String timeoutForPageLoad;
	private static String timeoutForCondition;
	private static String timeoutForSikuli;
	private static String similarityForSikuli;
	private static String detectJsErrors;
	private static String troubleShooting;
	private static String analyzeTraffic;
	private static String jiraUrl;
	private static String jiraLogin;
	private static String jiraPassword;

	public static Browsers currentBrowser;
	public static String stageProperty;

	/**
	 * Private constructor (singleton pattern)
	 */
	private Browser() {
		Logger.getInstance().info(String.format(getLoc("loc.browser.ready"), currentBrowser.toString()));
	}

	/**
	 * Подтверждение алерта
	 * @param waitTime - ожидание в секундах
	 */
	public static void confirmAlert(long waitTime) {
		WebDriverWait wait = new WebDriverWait(driver, waitTime);
		try {
			wait.until(ExpectedConditions.alertIsPresent());
			Alert alert = driver.switchTo().alert();
			logger.info("Переключение и подтверждение всплывшего сообщения. Текст алерта: "+alert.getText());
			alert.accept();
		} catch (Exception e) {
			Logger.getInstance().info("Alert pop-up didn't appear");
		}
	}

	/**
	 * Checks is Browser alive
	 * @return true\false
	 */
	public boolean isBrowserAlive() {
		return instance != null;
	}

	/**
	 * TimeoutForSikuli
	 * @return getTimeoutForSikuli
	 */
	public static String getTimeoutForSikuli() {
		return timeoutForSikuli;
	}

	/**
	 * DetectJSErrors
	 * @return getDetectJsErrors
	 */
	public static Boolean getDetectJsErrors() {
		return detectJsErrors.equalsIgnoreCase("true");
	}

	/**
	 * DetectJSErrors
	 * @return getTroubleShooting
	 */
	public static Boolean getTroubleShooting() {
		return troubleShooting.equalsIgnoreCase("true");
	}
	
	/**
	 * getTroubleShooting
	 * @return getTroubleShooting
	 */
	public static Boolean getAnalyzeTraffic() {
		return analyzeTraffic.equalsIgnoreCase("true");
	}
	
	/**
	 * Get Jira Url
	 * @return getJiraUrl
	 */
	public static String getJiraUrl() {
		return jiraUrl;
	}
	
	/**
	 * Get Jira Url
	 * @return getJiraUrl
	 */
	public static String getJiraLogin() {
		return jiraLogin;
	}
	
	/**
	 * Get Jira Url
	 * @return getJiraUrl
	 */
	public static String getJiraPassword() {
		return jiraPassword;
	}
	
	/**
	 * SimilarityForSikuli
	 * @return SimilarityForSikuli
	 */
	public static String getSimilarityForSikuli() {
		return similarityForSikuli;
	}

	/**
	 * Gets instance of Browser
	 * @return browser instance
	 */
	public static Browser getInstance() {
		if (instance == null) {
			initProperties();
			try {
				driver = BrowserFactory.setUp(currentBrowser.toString());
				driver.manage().timeouts().implicitlyWait(IMPLICITY_WAIT, TimeUnit.SECONDS);
				Logger.getInstance().info(getLoc("loc.browser.constructed"));
			} catch (NamingException e) {
				Logger.getInstance().info("NamingException...");
				e.printStackTrace();
			}
			instance = new Browser();
		}
		return instance;
	}

	/**
	 * The implementation of the browser is closed
	 * <p>
	 * see {@link BaseEntity#checkAndKill()} all browser processes will be killed
	 * <p>
	 * void after test
	 */
	public void exit() {
		try {
			driver.quit();
			Logger.getInstance().info(getLoc("loc.browser.driver.qiut"));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			instance = null;
		}
	}

	/**
	 * gets TimeoutForCondition
	 * @return timeoutForCondition
	 */
	public String getTimeoutForCondition() {
		return timeoutForCondition;
	}

	/**
	 * gets TimeoutForPageLoad
	 * @return timeoutForPageLoad
	 */
	public String getTimeoutForPageLoad() {
		return timeoutForPageLoad;
	}

	/**
	 * gets StartBrowserURL
	 * @return browserURL
	 */
	public String getStartBrowserURL() {
		return browserURL;
	}

	/**
	 * init
	 */
	private static void initProperties() {

		props = new PropertiesResourceManager(PROPERTIES_FILE);
		timeoutForPageLoad = props.getProperty(DEFAULT_PAGE_LOAD_TIMEOUT);
		timeoutForCondition = props.getProperty(DEFAULT_CONDITION_TIMEOUT);
		timeoutForSikuli = props.getProperty(DEFAULT_SIKULI_TIMEOUT, "10000");
		similarityForSikuli = props.getProperty(DEFAULT_SIKULI_SIMILARITY, "0.9");
		String url = props.getProperty(START_URL, "google.com");
		browserURL = String.format(props.getProperty(URL_LOGIN_PAGE), url);
		detectJsErrors = props.getProperty(DETECT_JS_ERRORS, "false");
		troubleShooting = props.getProperty(TROUBLE_SHOOTING, "false");
		analyzeTraffic = props.getProperty(ANALYZE_TRAFFIC, "false");

		if (Strings.isNullOrEmpty(props.getProperty(BROWSER_PROP))) {
			// using System.getProperty
			currentBrowser = Browsers.valueOf(System.getProperty(BROWSER_PROP, BROWSER_BY_DEFAULT).toUpperCase());
		} else {
			// using selenium.properties
			String proper = props.getProperty(BROWSER_PROP);
			currentBrowser = Browsers.valueOf(proper.toUpperCase());
		}
	}

	/**
	 * wait the download page (on Javascript readyState)
	 */
	public void waitForPageToLoad() {
		// Logger.getInstance().info("waitForPageToLoad started");
		WebDriverWait wait = new WebDriverWait(driver, Long.parseLong(getTimeoutForPageLoad()));

		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(final WebDriver d) {
					if (!(d instanceof JavascriptExecutor)) {
						return true;
					}
					Object result = ((JavascriptExecutor) d)
							.executeScript("return document['readyState'] ? 'complete' == document.readyState : true");
					if (result != null && result instanceof Boolean && (Boolean) result) {
						return true;
					}
					return false;
				}
			});
		} catch (Exception e) {
			Logger.getInstance().warn(getLoc("loc.browser.page.timeout"));
		}
		// Logger.getInstance().info("waitForPageToLoad ended");
	}

	/**
	 * waiting, while number of open windows will be more than previous
	 * @param prevWndCount - number of previous
	 */
	public void waitForNewWindow(final int prevWndCount) {
		final int wndCount = prevWndCount;
		WebDriverWait wait = new WebDriverWait(driver, Long.parseLong(getTimeoutForCondition()));
		try {
			wait.until((ExpectedCondition<Boolean>) new ExpectedCondition<Boolean>() {
				public Boolean apply(final WebDriver d) {
					return d.getWindowHandles().size() > wndCount;
				}
			});
		} catch (Exception e) {
			Assert.assertTrue(getLoc("loc.browser.newwindow.notappear"), false);
		}
	}

	/**
	 * Select the previous window (the list of handlers)
	 */
	public void selectPreviousWindow() {
		Object[] handles = driver.getWindowHandles().toArray();
		String handle = driver.getWindowHandle();
		Assert.assertTrue(getLoc("loc.browser.windows.count.small"), handles.length > 1);
		for (int i = 1; i < handles.length; i++) {
			if (handles[i].equals(handle)) {
				driver.switchTo().window((String) handles[i - 1]);
				return;
			}
		}
		driver.switchTo().window((String) handles[handles.length - 2]);

	}

	/**
	 * Select the first window (the list of handlers)
	 */
	public void selectFirstWindow() {
		Object[] handles = driver.getWindowHandles().toArray();
		driver.switchTo().window((String) handles[0]);

	}

	/**
	 * We expect the emergence of the new window and select it
	 */
	public void selectNewWindow() {
		Object[] handles = driver.getWindowHandles().toArray();
		waitForNewWindow(handles.length);
		handles = driver.getWindowHandles().toArray();
		driver.switchTo().window((String) handles[handles.length - 1]);

	}

	/**
	 * Select the last window (the list of handlers)
	 */
	public void selectLastWindow() {
		Object[] handles = driver.getWindowHandles().toArray();
		driver.switchTo().window((String) handles[handles.length - 1]);

	}

	/**
	 * Go to the home page
	 */
	public void openStartPage() {
		driver.navigate().to(props.getProperty(getStartBrowserURL()));
	}

	/**
	 * maximizes the window
	 * <p>
	 * works on IE7, IE8, IE9, FF 3.6
	 */
	public void windowMaximise() {
		try {
			driver.executeScript("if (window.screen) {window.moveTo(0, 0);window.resizeTo(window.screen.availWidth,window.screen.availHeight);};");
			driver.manage().window().maximize();
		} catch (Exception e) {
			//A lot of browsers crash here
		}

	}

	/**
	 * Navgates to the Url
	 * @param url Url
	 */
	public void navigate(final String url) {
		driver.navigate().to(url);
		logger.info("Переход по ссылке: "+url);
	}

	/**
	 * Refresh page.
	 */
	public void refresh() {
		driver.navigate().refresh();
		Logger.getInstance().info("Page was refreshed.");
	}

	/**
	 * get RemoteWebDriver
	 * @return driver
	 */
	public RemoteWebDriver getDriver() {
		return driver;
	}

	/**
	 * Open new window by hot keys Ctrl + N, webdriver switch focus on it.
	 */
	public void openNewWindow() {
		getDriver().findElement(By.xpath("//body")).sendKeys(Keys.CONTROL, "n");
		Object[] headers = getDriver().getWindowHandles().toArray();
		getDriver().switchTo().window(headers[headers.length - 1].toString());
	}

	/**
	 * click and switch to new window. (works on IE also)
	 * @param element element
	 */
	public void clickAndSwitchToNewWindow(final BaseElement element) {
		Set<String> existingHandles = getDriver().getWindowHandles();
		element.click();

		String foundHandle = null;
		long endTime = System.currentTimeMillis() + SLEEP_SWITCH;
		while (foundHandle == null && System.currentTimeMillis() < endTime) {
			Set<String> currentHandles = getDriver().getWindowHandles();
			if (currentHandles.size() != existingHandles.size()) {
				for (String currentHandle : currentHandles) {
					if (!existingHandles.contains(currentHandle)) {
						foundHandle = currentHandle;
						//Logger.getInstance().info("new window was found");
						break;
					}
				}

			}

			if (foundHandle == null) {
				try {
					Thread.sleep(SLEEP_THREAD_SLEEP);
				} catch (InterruptedException e) {
					Logger.getInstance().fatal("new window not found");
					e.printStackTrace();
				}
			}
		}

		if (foundHandle != null) {
			getDriver().switchTo().window(foundHandle);
		} else {
			Logger.getInstance().fatal("new window not found");
		}
	}

	/**
	 * Trigger
	 * @param script script
	 * @param element element
	 */
	public void trigger(final String script, final WebElement element) {
		((JavascriptExecutor) driver).executeScript(script, element);
	}

	/**
	 * Executes a script
	 * @note Really should only be used when the web driver is sucking at exposing functionality natively
	 * @param script The script to execute
	 * @return Object
	 */
	public Object trigger(final String script) {
		return ((JavascriptExecutor) driver).executeScript(script);
	}

	/**
	 * Opens a new tab for the given URL (doesn't work on IE)
	 * @param url The URL to
	 */
	public void openTab(final String url) {
		String script = "var d=document,a=d.createElement('a');a.target='_blank';a.href='%s';a.innerHTML='.';d.body.appendChild(a);return a";
		Object element = trigger(String.format(script, url));
		if (element instanceof WebElement) {
			WebElement anchor = (WebElement) element;
			anchor.click();
			trigger("var a=arguments[0];a.parentNode.removeChild(a);", anchor);
		} else {
			throw new JavaScriptException(element, "Unable to open tab", 1);
		}
	}

	/**
	 * Gets current URL
	 * @return current URL
	 */
	public String getLocation() {
		return driver.getCurrentUrl();
	}

	/**
	 * Executes Java Scipt
	 * @param script Java Script
	 */
	public void jsExecute(final String script) {
		JavascriptExecutor js = (JavascriptExecutor) getDriver();
		js.executeScript(script);
	}

	/**
	 * Sleeps for amount of time
	 * @param mSeconds milli seconds
	 */
	@Deprecated
	public void sleep(final long mSeconds) {
		try {
			Thread.sleep(mSeconds);
		} catch (InterruptedException e1) {
			e1.printStackTrace();
		}
	}

	/**
	 * Драгает элемент на offset пикселей по оси Х
	 * @param el элемент
	 * @param offset сдвиг
	 */
	public void dragElement(BaseElement el, int offset){
		Actions dragger = new Actions(driver);
		WebElement draggableElement = el.getElement();
		dragger.moveToElement(draggableElement).clickAndHold().moveByOffset(offset,0).release().perform();
	}

	/**
	 * Browsers enumeration
	 */
	public enum Browsers {
		/**
		 * @uml.property name="fIREFOX"
		 * @uml.associationEnd
		 */
		FIREFOX("firefox"), /**
		 * @uml.property name="iEXPLORE"
		 * @uml.associationEnd
		 */
		IEXPLORE("iexplore"), /**
		 * @uml.property name="cHROME"
		 * @uml.associationEnd
		 */
		CHROME("chrome"), /** 
		 * @uml.property name="oPERA"
		 * @uml.associationEnd
		 */
		OPERA("opera"), /** 
		 * @uml.property name="sAFARI"
		 * @uml.associationEnd
		 */
		 SAFARI("safari");
		
		public String value;

		/**
		 * Constructor
		 * @param values Value
		 */
		Browsers(final String values) {
			value = values;
		}

		/**
		 * Returns string value
		 * @return String value
		 */
		public String toString() {
			return value;
		}
	}
}
